/*

Copyright (C) 2007 Thomas Geiger (tom.geiger@gmail.com)

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/

// Author: Thomas Geiger
// Proxy support by Manfred Gschweidl (manfred.gschweidl@students.fhv.at)

#include "GlobusCredMgr.h"

namespace globus_common {

//used for password authentication
//Globus-C api wants a c-style funtion pointer => static memeber function
GlobusCredMgr* __globalCallbackObjectGCM = NULL;

//password authentication
int GlobusCredMgr::staticPWStdinCallback(char * buf, int num, int w) {
	int i;
	string pass = __globalCallbackObjectGCM->getKeyPassword();
	istringstream pwss(pass);
	pwss.getline(buf, num);
    i = strlen(buf);
	if (buf[i-1] == '\n') {
		buf[i-1] = '\0';
		i--;
	}

	return i;
}

//my proxy password authentication
int GlobusCredMgr::readMyProxyPassphrase(char *buffer, const int buffer_len)
{
    int	i;
	string pass = this->getKeyPassword();
	istringstream pwss(pass);
	pwss.getline(buffer, buffer_len);
    i = strlen(buffer);
	if (buffer[i-1] == '\n') {
		buffer[i-1] = '\0';
		i--;
	}

	return i;
}

GlobusCredMgr::GlobusCredMgr()
{
	globus_module_activate(GLOBUS_GSI_PROXY_MODULE);
	globus_module_activate(GLOBUS_GSI_CALLBACK_MODULE);
	globus_module_activate(GLOBUS_GSI_CERT_UTILS_MODULE);
	globus_module_activate(GLOBUS_GSI_GSS_ASSIST_MODULE);	
	globus_module_activate(GLOBUS_GSI_CREDENTIAL_MODULE);
	
	proxy_handle = NULL;
	proxy_handle_attrs = NULL;
	callback_data = NULL;
	cred_handle_attrs = NULL;
	cred_handle = NULL;
	proxy_cred_handle = NULL;
	key_bits = 512; //key length
	valid = 12 * 60; //hours * minutes
	lifetime = 0;
	goodtill = 0;
	cert_type = GLOBUS_GSI_CERT_UTILS_TYPE_GSI_3_IMPERSONATION_PROXY;

	key_password = "";
	user_cert_filename = NULL;
	user_key_filename = NULL;
	proxy_out_filename = NULL;

	subject_name = NULL;
}

GlobusCredMgr::~GlobusCredMgr()
{
	proxy_handle = NULL;
	proxy_handle_attrs = NULL;
	callback_data = NULL;
	cred_handle_attrs = NULL;
	cred_handle = NULL;
	proxy_cred_handle = NULL;
	
	user_cert_filename = NULL;
	user_key_filename = NULL;
	proxy_out_filename = NULL;
	
	subject_name = NULL;
	
	globus_module_deactivate(GLOBUS_GSI_GSS_ASSIST_MODULE);
	globus_module_deactivate(GLOBUS_GSI_CERT_UTILS_MODULE);
	globus_module_deactivate(GLOBUS_GSI_PROXY_MODULE);
	globus_module_deactivate(GLOBUS_GSI_CALLBACK_MODULE);
	globus_module_deactivate(GLOBUS_GSI_CREDENTIAL_MODULE);
}

void GlobusCredMgr::destroyCredential() {
   major_status = globus_gsi_cred_handle_destroy(cred_handle);
   test_error(__LINE__);
}

void GlobusCredMgr::destroyProxy() {
	major_status = globus_gsi_cred_handle_destroy(proxy_cred_handle);
	test_error(__LINE__);
    major_status = globus_gsi_proxy_handle_destroy(proxy_handle);
    test_error(__LINE__);
    major_status = globus_gsi_cred_handle_destroy(cred_handle);
    test_error(__LINE__);
	this->destroyCredentialHandle(cred_handle);
    this->destroyCredentialHandle(proxy_cred_handle);
}

void GlobusCredMgr::readCredentialX509SubjectName() {
	major_status = globus_gsi_cred_get_X509_subject_name(cred_handle, &subject_name);
	test_error(__LINE__);
}

string GlobusCredMgr::getCertificateSubject() {
	char* subject = NULL;
	int keyb = 0;
	
	//get user cert file system default / environment variables
	if(!this->getUserCertFile()) {
		return string();		
	}
	//credential initialization
	this->initCredential();
	//read credential certificate
	this->readCredentialCert();
	
	major_status = globus_gsi_cred_get_subject_name(cred_handle, &subject);
	if(test_error(__LINE__)) {
		return string();
	}
	
	major_status = globus_gsi_cred_get_key_bits(cred_handle, &keyb);
	if(test_error(__LINE__)) {
		return string();
	}
	
	cout << "Subject: " << subject << endl;
	readCredentialGoodTill();
	readCredentialX509SubjectName();
	cout << "X509 Subject Name: " << subject_name << endl;
	cout << "Key Bits: " << keyb << endl;
	return string(subject);
}

string GlobusCredMgr::getProxySubject() {
	char* subject = NULL;
	char* issuer = NULL;
	char* identity = NULL;
	X509* proxy_cert = NULL;
	EVP_PKEY* pub_key = NULL;
	time_t lifetime;
	int strength = 0;
	
	getProxyOutFile();
	major_status = GLOBUS_GSI_SYSCONFIG_CHECK_KEYFILE(proxy_out_filename);
	if(test_error(__LINE__)) {
		return string();
	}
	
	major_status = globus_gsi_cred_handle_init(&proxy_cred_handle, cred_handle_attrs);
	test_error(__LINE__);
	major_status = globus_gsi_cred_read_proxy(proxy_cred_handle, proxy_out_filename);
	test_error(__LINE__);
	major_status = globus_gsi_cred_get_cert(proxy_cred_handle, &proxy_cert);
	test_error(__LINE__);
	if((pub_key = X509_get_pubkey(proxy_cert)) == NULL) {
		cout << "error reading public key from proxy" << endl;
		return string();	
	}
	
	major_status = globus_gsi_cred_get_subject_name(proxy_cred_handle, &subject);
	if(test_error(__LINE__)) {
		return string();
	}
	
	major_status = globus_gsi_cred_get_issuer_name(proxy_cred_handle, &issuer);
	if(test_error(__LINE__)) {
		return string();
	}
	
	major_status = globus_gsi_cred_get_identity_name(proxy_cred_handle, &identity);
	if(test_error(__LINE__)) {
		return string();
	}
	
	/* type: restricted, limited or full */
    major_status = globus_gsi_cred_get_cert_type(proxy_cred_handle,
                                           &cert_type);
    if(test_error(__LINE__)) {
		return string();
	}
	
	major_status = globus_gsi_cred_get_lifetime(proxy_cred_handle, &lifetime);
	if(test_error(__LINE__)) {
		return string();
	}
	
	strength = 8 * EVP_PKEY_size(pub_key);
	//format lifetime
	if (lifetime <= 0)
		lifetime = 0;

	long lifetime_hour = (long)(lifetime / 3600);
	long lifetime_minutes = (long)(lifetime / 3600);
	long lifetime_seconds = (long)(lifetime % 60 );
	long lifetime_days = 0;
	if (lifetime > 3600 * 24)
		lifetime_days = (long)((float)((lifetime / 3600) / 24.0));
	
	cout << "subject: " << subject << endl;
	cout << "issuer: " << subject << endl;
	cout << "identity: " << subject << endl;
	cout << "type: " << getCertificateType(cert_type) << endl;
	cout << "strength: " << strength << " bits" << endl;
	cout << "path: " << proxy_out_filename << endl;
	cout << "timeleft: " << lifetime_hour << ":" << lifetime_minutes << ":" << lifetime_seconds;
	if(lifetime_days > 0) cout << " " << lifetime_days << " days" << endl;
	else cout << endl;
	if(subject != NULL) return string(subject);
	else return string();
}

string GlobusCredMgr::getCertificateType(globus_gsi_cert_utils_cert_type_t cert_type) {
	string cert_type_name;
    switch(cert_type)
    {
      case GLOBUS_GSI_CERT_UTILS_TYPE_RFC_IMPERSONATION_PROXY:
        cert_type_name = "RFC 3820 compliant impersonation proxy";
        break;
      case GLOBUS_GSI_CERT_UTILS_TYPE_RFC_INDEPENDENT_PROXY:
        cert_type_name = "RFC 3820 compliant independent proxy";
        break;
      case GLOBUS_GSI_CERT_UTILS_TYPE_RFC_LIMITED_PROXY:
        cert_type_name = "RFC 3820 compliant limited proxy";
        break;
      case GLOBUS_GSI_CERT_UTILS_TYPE_RFC_RESTRICTED_PROXY:
        cert_type_name = "RFC 3820 compliant restricted proxy";
        break;
      case GLOBUS_GSI_CERT_UTILS_TYPE_GSI_3_IMPERSONATION_PROXY:
        cert_type_name = "Proxy draft (pre-RFC) compliant impersonation proxy";
        break;
      case GLOBUS_GSI_CERT_UTILS_TYPE_GSI_3_INDEPENDENT_PROXY:
        cert_type_name = "Proxy draft (pre-RFC) compliant independent proxy";
        break;
      case GLOBUS_GSI_CERT_UTILS_TYPE_GSI_3_LIMITED_PROXY:
        cert_type_name = "Proxy draft (pre-RFC) compliant limited proxy";
        break;
      case GLOBUS_GSI_CERT_UTILS_TYPE_GSI_3_RESTRICTED_PROXY:
        cert_type_name = "Proxy draft (pre-RFC) compliant restricted proxy";
        break;
      case GLOBUS_GSI_CERT_UTILS_TYPE_GSI_2_PROXY:
        cert_type_name = "full legacy globus proxy";
        break;
      case GLOBUS_GSI_CERT_UTILS_TYPE_GSI_2_LIMITED_PROXY:
        cert_type_name = "limited legacy globus proxy";
        break;
      case GLOBUS_GSI_CERT_UTILS_TYPE_EEC:
        cert_type_name = "end entity credential";
        break;        
      default:
      	cout << "error: not a proxy" << endl;
		return string();
    }
    return cert_type_name;
}

//needs to be bool
bool GlobusCredMgr::destroyProxyFile() {
	this->getProxyOutFile();
	remove(proxy_out_filename);
	if(!this->hasCredential()) {
		return true;
	} else {
		return false;
	}
}

void GlobusCredMgr::initCredentialAttributes() {
	major_status = globus_gsi_cred_handle_attrs_init(&cred_handle_attrs);
	test_error(__LINE__);
}

void GlobusCredMgr::destroyCredentialAttributes() {
	major_status = globus_gsi_cred_handle_attrs_destroy(cred_handle_attrs);
	test_error(__LINE__);	
}

void GlobusCredMgr::initCredential() {
	//credential attributes intialization
	this-> initCredentialAttributes();
	//init credential
	major_status = globus_gsi_cred_handle_init(&cred_handle, cred_handle_attrs);
	if (major_status != GSS_S_COMPLETE) {
		globus_gss_assist_display_status(stdout,
					"Some failure message here",
					major_status,
					minor_status,
					0);
	}	
	//destroy credentail Attributes
	this->destroyCredentialAttributes();
}

void GlobusCredMgr::destroyCredentialHandle(globus_gsi_cred_handle_t handle) {
	major_status = globus_gsi_cred_handle_destroy(handle);
	test_error(__LINE__);
}

void GlobusCredMgr::readCredentialCert() {
	major_status = globus_gsi_cred_read_cert(
            cred_handle,
            user_cert_filename);	
  if (major_status != GSS_S_COMPLETE) {
    globus_gss_assist_display_status(stdout,
				     "Some failure message here",
				     major_status,
				     minor_status,
				     0);
  }	
}

void GlobusCredMgr::readCredentialKey() {
	__globalCallbackObjectGCM = this;
	
	int (*fp_pw)() = NULL;
	fp_pw = (int(*)())GlobusCredMgr::staticPWStdinCallback;
	major_status = globus_gsi_cred_read_key(
            cred_handle,
            user_key_filename,
            fp_pw);
    //todo: check error if key was sealed with passphrase and openssl error
    __globalCallbackObjectGCM = NULL;
	if (major_status != GSS_S_COMPLETE) {
			globus_gss_assist_display_status(stdout,
					"Some failure message here",
					major_status,
					minor_status,
					0);
	}
}

void GlobusCredMgr::createProxy() {
	major_status = globus_gsi_proxy_create_signed(
        proxy_handle,
        cred_handle,
        &proxy_cred_handle);
	test_error(__LINE__);	
}

void GlobusCredMgr::verifyProxy() {
	major_status = globus_gsi_cred_verify(proxy_cred_handle);
	test_error(__LINE__);	
}

void GlobusCredMgr::writeProxy() {
	cout << "write proxy file: " << proxy_out_filename << endl;
	major_status = globus_gsi_cred_write_proxy(proxy_cred_handle,
                                         proxy_out_filename);
	test_error(__LINE__);
}

bool GlobusCredMgr::hasCredential()
{
	major_status = globus_gss_assist_acquire_cred(&minor_status,
	                                            GSS_C_INITIATE,
	                                            &handle);	
	if (major_status == GSS_S_COMPLETE) {
		return true;	
	}
	return false;
}

void GlobusCredMgr::readCredentialLifetime() {
    major_status = globus_gsi_cred_get_lifetime(
        cred_handle,
        &lifetime);
    test_error(__LINE__);
}

void GlobusCredMgr::readCredentialGoodTill() {
	if(proxy_cred_handle == NULL) {
	    major_status = globus_gsi_cred_get_goodtill(
	        cred_handle,
	        &goodtill);
	} else {
	    major_status = globus_gsi_cred_get_goodtill(
	        proxy_cred_handle,
	        &goodtill);
	}
	test_error(__LINE__);
}

void GlobusCredMgr::initProxyAttributes() {
	//init proxy attrs
	major_status = globus_gsi_proxy_handle_attrs_init(&proxy_handle_attrs);
	test_error(__LINE__);
	
	major_status = globus_gsi_proxy_handle_attrs_set_keybits(
        proxy_handle_attrs, key_bits);
	test_error(__LINE__);
}

void GlobusCredMgr::destroyProxyAttributes() {
	major_status = globus_gsi_proxy_handle_attrs_destroy(proxy_handle_attrs);
	test_error(__LINE__);	
}

void GlobusCredMgr::setProxyCertificateLifetime() {
	major_status = globus_gsi_proxy_handle_set_time_valid(proxy_handle, valid);
	test_error(__LINE__);	
}

void GlobusCredMgr::setProxyCertificateType() {
	major_status = globus_gsi_proxy_handle_set_type(proxy_handle, cert_type);
	test_error(__LINE__);	
}

void GlobusCredMgr::initProxy() {
	major_status = globus_gsi_proxy_handle_init(&proxy_handle, proxy_handle_attrs);
	test_error(__LINE__);	
}

bool GlobusCredMgr::aquireCredential(string pass) {
	this->trimSpaces(pass);
	this->key_password = pass;
	//init proxy attrs
	this->initProxyAttributes();
	//init proxy
	this->initProxy();
	//detroy proxy attrs
	this->destroyProxyAttributes();
	//get user cert file system default / environment variables
	if(!this->getUserCertFile())
	{
		return false;
	}
	//get proxy out file system default and check directory
	this->getProxyOutFile();
	//set proxy lifetime
	this->setProxyCertificateLifetime();
	//set proxy type
	this->setProxyCertificateType();
	//credential initialization
	this->initCredential();
	//read credential certificate
	this->readCredentialCert();
	//read private key
	this->readCredentialKey();
	//now ready for proxy action
	this->createProxy();
	//verify proxy
	this->verifyProxy();
	//write proxy
	this->writeProxy();
	//check lifetime of proxy certificate
	//this->checkCredentialLifetime();
	return true;
}

bool GlobusCredMgr::getUserCertFile() {
	major_status = GLOBUS_GSI_SYSCONFIG_GET_USER_CERT_FILENAME(&user_cert_filename, &user_key_filename);
	if(test_error(__LINE__)) {
		return false;
	} else {
		return true;
	}
}

void GlobusCredMgr::getProxyOutFile() {
	char* proxy_absolute_path;
	char* temp_dir;
	char* temp_filename;

    if(proxy_out_filename == NULL)
    {
	major_status = GLOBUS_GSI_SYSCONFIG_GET_PROXY_FILENAME(
            &proxy_out_filename,
            GLOBUS_PROXY_FILE_OUTPUT);
        if(major_status != GLOBUS_SUCCESS)
        {
        	cerr << "\n\nERROR: Couldn't find a valid location to write the proxy file\n";
        }
    }
	major_status = GLOBUS_GSI_SYSCONFIG_MAKE_ABSOLUTE_PATH_FOR_FILENAME(
            proxy_out_filename,
            &proxy_absolute_path);
	proxy_out_filename = proxy_absolute_path;
	major_status = GLOBUS_GSI_SYSCONFIG_SPLIT_DIR_AND_FILENAME(
            proxy_absolute_path,
            &temp_dir,
            &temp_filename);
    major_status = GLOBUS_GSI_SYSCONFIG_DIR_EXISTS(temp_dir);
    test_error(__LINE__);
    //true proxy_out_filename could be what it is
	//return true;
            
} 

bool GlobusCredMgr::releaseCredential() {
 	return this->destroyProxyFile();	
}

string GlobusCredMgr::getKeyPassword() {
	return this->key_password;
}

bool GlobusCredMgr::aquireCredentialMyProxy(string myproxyServer = "", string pass = "") {
    myproxy_socket_attrs_t *socket_attrs = new myproxy_socket_attrs_t;
    myproxy_request_t      *client_request = new myproxy_request_t;
    myproxy_response_t     *server_response = new myproxy_response_t;
    int return_value = 1;
    
    int dn_as_username = 0;

    /* check library version */
    if (myproxy_check_version()) {
		fprintf(stderr, "MyProxy library version mismatch.\nExpecting %s.  Found %s.\n",
			MYPROXY_VERSION_DATE, myproxy_version(0,0,0));
		exit(1);
    }

    myproxy_log_use_stream (stderr);

	setvbuf((stdout), (char *) NULL, _IOLBF, 0);
	setvbuf((stderr), (char *) NULL, _IOLBF, 0);

    memset(socket_attrs, 0, sizeof(*socket_attrs));

    memset(client_request, 0, sizeof(*client_request));

    memset(server_response, 0, sizeof(*server_response));

    /* Setup defaults */
    myproxy_set_delegation_defaults(socket_attrs,client_request);

    /* Initialize client arguments and create client request object */
    this->trimSpaces(myproxyServer);
    if(!myproxyServer.empty()) {
		socket_attrs->pshost = (char*)myproxyServer.c_str();
    }

    /* Check to see if myproxy-server specified */
    if (socket_attrs->pshost == NULL) {
		fprintf(stderr, "Unspecified myproxy-server. Please set the MYPROXY_SERVER environment variable\nor set the myproxy-server hostname via the -s flag.\n");
		exit(1);
    }

    /* Connect to server. */
    if (myproxy_init_client(socket_attrs) < 0) {
        verror_print_error(stderr);
        goto cleanup;
    }
    
    if(proxy_out_filename != NULL) {
    	string tmpProxyFilename(proxy_out_filename);
    	this->trimSpaces(tmpProxyFilename);
    	if(tmpProxyFilename.empty()) {
			this->getProxyOutFile();
    	} else {
    		proxy_out_filename = (char*)tmpProxyFilename.c_str();
    	}
    } else {
		this->getProxyOutFile();    	
    }

	int rval;
	if(pass.size() > 0) {
		trimSpaces(pass);
	} else {
		pass = "";
	}

	if( !pass.empty() ) {
		/* Allow user to provide a passphrase */
		this->key_password = pass;	
		rval = this->readMyProxyPassphrase(client_request->passphrase, sizeof(client_request->passphrase));		
	} else {
	    rval = myproxy_read_passphrase(client_request->passphrase,
					   sizeof(client_request->passphrase),
					   NULL);
	}

	if (rval == -1) {
	    verror_print_error(stderr);
	    goto cleanup;
    }

   if (client_request->username == NULL) { /* set default username */
		if (dn_as_username) {
		    if (client_request->authzcreds) {
		    	/*
			if (ssl_get_base_subject_file(client_request->authzcreds,
						      &client_request->username)) {
			    fprintf(stderr, "Cannot get subject name from %s.\n",
				    client_request->authzcreds);
			    goto cleanup;
			}
			*/
		    } else {
		    	/*
			if (ssl_get_base_subject_file(NULL,
						      &client_request->username)) {
			    fprintf(stderr,
				    "Cannot get subject name from your certificate.\n");
			    goto cleanup;
			}
			*/
		    }
		} else {
		    char *username = NULL;
		    if (!(username = getenv("LOGNAME"))) {
			fprintf(stderr, "Please specify a username.\n");
			goto cleanup;
		    }
		    client_request->username = strdup(username);
		}
    }

    if (myproxy_get_delegation(socket_attrs, client_request, NULL,
			       server_response, proxy_out_filename)!=0) {
		fprintf(stderr, "Failed to receive credentials.\n");
		verror_print_error(stderr);
		goto cleanup;
    }
    
    cout << "write proxy file: " << proxy_out_filename << endl;
    //return_value = 0;
    return true;

	cleanup:
		/* free memory allocated */
		delete socket_attrs;
		delete client_request;
		delete server_response;
		//return return_value;
		return false;
}

void GlobusCredMgr::trimSpaces( string& str)
{
    // Trim Both leading and trailing spaces
	size_t startpos = str.find_first_not_of(" "); // Find the first character position after excluding leading blank spaces
	size_t endpos = str.find_last_not_of(" "); // Find the first character position from reverse af
    // if all spaces or empty return an empty string
	if(( string::npos == startpos ) || ( string::npos == endpos))
	{
		str = "";
	} else {
		str = str.substr( startpos, endpos-startpos+1 );
	}
}

bool GlobusCredMgr::test_error(int line)
{
    if(major_status == GLOBUS_SUCCESS)
    {
        return false;
    }

	cerr << "Code line: " << line << ", ";
	cerr << "ERROR: " << globus_error_print_friendly( globus_error_peek(major_status) ) << endl;

    //globus_assert(0);
    return true;
}

}
